<script setup lang="ts">
import { onMounted, ref } from 'vue'
import {
  NCollapse,
  NCollapseItem,
  NButton,
  NIcon,
  NSelect,
  NInput,
  NScrollbar
} from 'naive-ui'
import {
  Add20Regular,
  Delete16Regular
} from '@vicons/fluent'
import { EditorView, basicSetup } from 'codemirror'
import { EditorState } from '@codemirror/state'
import { autocompletion, CompletionContext } from '@codemirror/autocomplete'
import { python } from '@codemirror/lang-python'
import { barf } from 'thememirror'
import { NodeItem } from '@/types/node'

const node = defineModel<NodeItem>()

const editorDivRef = ref()
let editor!: EditorView
const types = [
  { type: 'number', name: '数字' },
  { type: 'string', name: '字符串' },
  { type: 'bool', name: '布尔类型' },
  { type: 'list', name: '列表' },
  { type: 'tuple', name: '元组' },
  { type: 'set', name: '集合' },
  { type: 'dictionary', name: '字典' },
  { type: 'mat', name: 'Mat' }
]
const inputs = ref<(typeof types[0])[]>(node.value?.inputs || [])
const outputs = ref<(typeof types[0])[]>(node.value?.outputs || [])
const name = ref(node.value?.name || '新节点')

function onAddInput () {
  inputs.value.push({} as Any)
}
function onAddOutput () {
  outputs.value.push({} as Any)
}
function onInputRemove (i: number) {
  inputs.value.splice(i, 1)
}
function onOutputRemove (i: number) {
  outputs.value.splice(i, 1)
}
function validName (v: string) {
  return /[a-zA-Z][a-zA-Z0-9]{0,11}/.test(v)
}
function baseCompletion (context: CompletionContext) {
  const word = context.matchBefore(/\w*/)
  if (!word) {
    return null
  }
  if (word.from === word.to && !context.explicit) return null
  return {
    from: word.from,
    options: [
      ...inputs.value.map(v => ({
        label: v.name, type: 'variable', detail: `输入参数 ${v.name}: ${v.type}`
      })),
      ...outputs.value.map(v => ({
        label: v.name, type: 'variable', detail: `输出参数 ${v.name}: ${v.type}`
      }))
    ]
  }
}
function genID () {
  let id = 'abcdefghijklmnopqrstuvwxyz'[Math.round(Math.random() * 25)]
  for (let i = 0; i < 7; i++) {
    id += 'abcdefghijklmnopqrstuvwxyz0123456789'[Math.round(Math.random() * 35)]
  }
  return id
}

onMounted(() => {
  editor = new EditorView({
    extensions: [basicSetup, python(), barf, EditorState.tabSize.of(2), autocompletion({ override: [baseCompletion] })],
    parent: editorDivRef.value as HTMLDivElement,
    doc: node.value?.script || ''
  })
})

defineExpose({
  reset () {
    editor.dispatch({ changes: { from: 0, to: editor.state.doc.length, insert: '' } })
    inputs.value = []
    outputs.value = []
  },
  json () {
    if (!name.value) {
      throw '输入节点名'
    }
    if (inputs.value.filter(v => !v.name || !v.type).length > 0) {
      throw '输入参数异常'
    }
    if (outputs.value.filter(v => !v.name || !v.type).length > 0) {
      throw '输出参数异常'
    }
    const script = editor.state.doc.toString()
    if (!script) {
      throw '请输入脚本'
    }
    return {
      id: node.value?.id || genID(),
      name: name.value,
      script,
      inputs: inputs.value,
      outputs: outputs.value
    }
  }
})
</script>

<template>
  <div class="node-editor">
    <div class="side-bar">
      <div class="node-name">
        <label>节点名:</label>
        <n-input v-model:value="name" :bordered="false" placeholder="输入节点名称" />
      </div>
      <n-collapse class="io" :expanded-names="['input', 'output']">
        <template #arrow><span></span></template>
        <n-collapse-item title="输入参数" name="input">
          <template #header-extra>
            <n-button text @click.stop="onAddInput">
              <n-icon size="20">
                <Add20Regular />
              </n-icon>
            </n-button>
          </template>
          <n-scrollbar>
            <div class="io-item" v-for="(item, i) of inputs" :key="i">
              <n-input v-model:value="item.name" :bordered="false" :allow-input="validName" placeholder="名称"></n-input>
              <n-select v-model:value="item.type" :options="types" label-field="name" value-field="type" :bordered="false" placeholder="类型" />
              <n-button text type="error" @click="onInputRemove(i)">
                <n-icon size="18"><Delete16Regular /></n-icon>
              </n-button>
            </div>
          </n-scrollbar>
        </n-collapse-item>
        <n-collapse-item title="输出参数" name="output">
          <template #header-extra>
            <n-button text @click.stop="onAddOutput">
              <n-icon size="20">
                <Add20Regular />
              </n-icon>
            </n-button>
          </template>
          <n-scrollbar>
            <div class="io-item" v-for="(item, i) of outputs" :key="i">
              <n-input v-model:value="item.name" :bordered="false" :allow-input="validName" placeholder="名称"></n-input>
              <n-select v-model:value="item.type" :options="types" label-field="name" value-field="type" :bordered="false" placeholder="类型" />
              <n-button text type="error" @click="onOutputRemove(i)">
                <n-icon size="18"><Delete16Regular /></n-icon>
              </n-button>
            </div>
          </n-scrollbar>
        </n-collapse-item>
      </n-collapse>
    </div>
    <div class="script" ref="editorDivRef">
    </div>
  </div>
</template>
<style scoped lang="less">
.node-editor {
  height: 100%;
  display: flex;
  .n-collapse {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    :deep(.n-collapse-item) {
      margin: 0 !important;
      flex-basis: 50%;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      overflow: hidden;
      .n-collapse-item__header {
        background-color: #FFFFFF04;
        padding: 8px !important;
        flex-shrink: 0;
        border-top: 1px solid var(--n-divider-color);

        &.n-collapse-item__header--active {
          border-bottom: 1px solid var(--n-divider-color);
        }

        .n-collapse-item-arrow {
          transform: rotate(0) !important;
          margin-right: 5px;
        }
      }
      .n-collapse-item__content-wrapper {
        flex-grow: 1;
        overflow: hidden;
      }
      .n-collapse-item__content-inner {
        padding: 0 !important;
        height: 100%;

        .n-scrollbar-content {
          padding: 0 3px;
        }
      }
    }
  }
  .side-bar {
    width: 240px;
    flex-shrink: 0;
    border-right: 1px solid var(--n-border-color);
    display: flex;
    flex-direction: column;
    .node-name {
      margin: 5px;
      width: calc(100% - 10px);
      display: flex;
      align-items: center;
      label {
        white-space: nowrap;
        margin: 0 5px;
      }
    }
    .io {
      flex-grow: 1;
      overflow: hidden;
      &-item {
        display: flex;
        margin-top: 3px;
        .n-input {
          margin-right: 3px;
        }
        .n-select {
          width: 210px;
          margin-right: 3px;
        }
      }
    }
  }
  .script {
    flex-grow: 1;
    overflow: hidden;
    :deep(.cm-editor) {
      height: 100%;
      font-size: 18px;
    }
  }
}
</style>
